﻿using System;
using System.Numerics;
//using System.Collections.Generic;
//using System.Linq;
//using System.Text;
using System.Threading.Tasks;
using GKS.Extensions;

namespace GKS.DataTypes.Math {
    /// <summary>
    /// A class that implements generic Matrices.
    /// </summary>
    public class Matrix<T> {
        #region variables
        //private double[,] elements;
        //private object thisLock = new object();
        //private object thisNumericElementsLoc = new object();
        //private object thisBigNumericElementsLoc = new object();
        //private object thisMatrixElementsLoc = new object();
        //private object elementsLoc = new object();
        private T[,] elements;
        #endregion

        #region properties
        ///// <summary>
        ///// Get the type of the elements contained in this Matrix.
        ///// </summary>
        //public ElementTypes ElementType { get; private set; }

        /// <summary>
        /// Get the number of the columns in this matrix.
        /// </summary>
        public int Columns { get; private set; }

        /// <summary>
        /// Get the number of rows in this matrix.
        /// </summary>
        public int Rows { get; private set; }

        /// <summary>
        /// Get the elements contained in this matrix.
        /// </summary>
        public T[,] Elements {
            get {
                lock (elements) {
                    return elements;
                }
            }
            private set {
                lock (elements) {
                    elements = value;
                }
            }
        }

        /// <summary>
        /// Get or Set the value of the element in the given position.
        /// </summary>
        /// <param name="row">
        /// The 0-based index of the row.
        /// </param>
        /// <param name="column">
        /// The 0-based index of the column.
        /// </param>
        /// <returns>
        /// The element in the specified position.
        /// </returns>
        /// <exception cref="ArgumentOutOfRangeException">
        /// Exception thrown in case the specified index is out of the allowed range.
        /// </exception>
        public T this[int row, int column] {
            get {
                if (row < 0) {
                    throw new IndexOutOfRangeException("The index of the row cannot be smaller than 0. The row index is 0-based.");
                }

                if (row > Elements.GetLength(0)) {
                    throw new IndexOutOfRangeException(
                        string.Format(
                            "The index of the row cannot be greater than {0}.", 
                            (Elements.GetLength(0) - 1)));
                }

                if (column < 0) {
                    throw new IndexOutOfRangeException("The index of the column cannot be smaller than 0. The row index is 0-based.");
                }

                if (column > Elements.GetLength(1)) {
                    throw new IndexOutOfRangeException(
                        string.Format(
                            "The index of the column cannot be greater than {0}.", 
                            (Elements.GetLength(1) - 1)));
                }

                T result;

                lock (elements) {
                    result = elements[row, column];
                }

                return result;
            }
            set {
                if (row < 0) {
                    throw new IndexOutOfRangeException("The index of the row cannot be smaller than 0. The row index is 0-based.");
                }

                if (row > Elements.GetLength(0)) {
                    throw new IndexOutOfRangeException(
                        string.Format(
                            "The index of the row cannot be greater than {0}.",
                            (Elements.GetLength(0) - 1)));
                }

                if (column < 0) {
                    throw new IndexOutOfRangeException("The index of the column cannot be smaller than 0. The row index is 0-based.");
                }

                if (column > Elements.GetLength(1)) {
                    throw new IndexOutOfRangeException(
                        string.Format(
                            "The index of the column cannot be greater than {0}.",
                            (Elements.GetLength(1) - 1)));
                }

                lock (elements) {
                    elements[row, column] = value;
                }
            }
        }

        ///// <summary>
        ///// Get the numeric elements contained in this Matrix.
        ///// </summary>
        //public Complex[,] NumericElements { get; private set; }

        ///// <summary>
        ///// Get the big numeric elements contained in this Matrix.
        ///// </summary>
        //public BigInteger[,] BigNumericElements { get; private set; }

        ///// <summary>
        ///// Get the Matrix elements contained in this Matrix.
        ///// </summary>
        //public Matrix<T>[,] MatrixElements { get; private set; }

        ///// <summary>
        ///// The indexing property of the Matrix.
        ///// </summary>
        ///// <param name="column">
        ///// The index of the column.
        ///// </param>
        ///// <param name="row">
        ///// The index of the row.
        ///// </param>
        ///// <returns>
        ///// The element in the specified position.
        ///// </returns>
        //public Complex this[int column, int row] {
        //    get {
        //        if (column < 0) {
        //            throw new ArgumentOutOfRangeException("column", "The index of the column must be greater than, or equal to, 0.");
        //        }

        //        if (column >= Columns) {
        //            throw new ArgumentOutOfRangeException("column", string.Format("The index of the column must be smaller than {0}.", Columns));
        //        }

        //        if (row < 0) {
        //            throw new ArgumentOutOfRangeException("row", "The index of the row must be greater than, or equal to, 0.");
        //        }

        //        if (row >= Rows) {
        //            throw new ArgumentOutOfRangeException("row", string.Format("The index of the column must be smaller than {0}.", Rows));
        //        }

        //        return NumericElements[column, row];
        //    }
        //    set {
        //        if (column < 0) {
        //            throw new ArgumentOutOfRangeException("column", "The index of the column must be greater than, or equal to, 0.");
        //        }

        //        if (column >= Columns) {
        //            throw new ArgumentOutOfRangeException("column", string.Format("The index of the column must be smaller than {0}.", Columns));
        //        }

        //        if (row < 0) {
        //            throw new ArgumentOutOfRangeException("row", "The index of the row must be greater than, or equal to, 0.");
        //        }

        //        if (row >= Rows) {
        //            throw new ArgumentOutOfRangeException("row", string.Format("The index of the column must be smaller than {0}.", Rows));
        //        }

        //        NumericElements[column, row] = value;
        //    }
        //}

        ///// <summary>
        ///// The indexing property of the Matrix.
        ///// </summary>
        ///// <param name="column">
        ///// The index of the column.
        ///// </param>
        ///// <param name="row">
        ///// The index of the row.
        ///// </param>
        ///// <returns>
        ///// The element in the specified position.
        ///// </returns>
        //public BigInteger this[int column, int row] {
        //    get {
        //        if (column < 0) {
        //            throw new ArgumentOutOfRangeException("column", "The index of the column must be greater than, or equal to, 0.");
        //        }

        //        if (column >= Columns) {
        //            throw new ArgumentOutOfRangeException("column", string.Format("The index of the column must be smaller than {0}.", Columns));
        //        }

        //        if (row < 0) {
        //            throw new ArgumentOutOfRangeException("row", "The index of the row must be greater than, or equal to, 0.");
        //        }

        //        if (row >= Rows) {
        //            throw new ArgumentOutOfRangeException("row", string.Format("The index of the column must be smaller than {0}.", Rows));
        //        }

        //        return BigNumericElements[column, row];
        //    }
        //    set {
        //        if (column < 0) {
        //            throw new ArgumentOutOfRangeException("column", "The index of the column must be greater than, or equal to, 0.");
        //        }

        //        if (column >= Columns) {
        //            throw new ArgumentOutOfRangeException("column", string.Format("The index of the column must be smaller than {0}.", Columns));
        //        }

        //        if (row < 0) {
        //            throw new ArgumentOutOfRangeException("row", "The index of the row must be greater than, or equal to, 0.");
        //        }

        //        if (row >= Rows) {
        //            throw new ArgumentOutOfRangeException("row", string.Format("The index of the column must be smaller than {0}.", Rows));
        //        }

        //        BigNumericElements[column, row] = value;
        //    }
        //}

        ///// <summary>
        ///// The indexing property of the Matrix.
        ///// </summary>
        ///// <param name="column">
        ///// The index of the column.
        ///// </param>
        ///// <param name="row">
        ///// The index of the row.
        ///// </param>
        ///// <returns>
        ///// The element in the specified position.
        ///// </returns>
        //public Matrix this[int column, int row] {
        //    get {
        //        if (column < 0) {
        //            throw new ArgumentOutOfRangeException("column", "The index of the column must be greater than, or equal to, 0.");
        //        }

        //        if (column >= Columns) {
        //            throw new ArgumentOutOfRangeException("column", string.Format("The index of the column must be smaller than {0}.", Columns));
        //        }

        //        if (row < 0) {
        //            throw new ArgumentOutOfRangeException("row", "The index of the row must be greater than, or equal to, 0.");
        //        }

        //        if (row >= Rows) {
        //            throw new ArgumentOutOfRangeException("row", string.Format("The index of the column must be smaller than {0}.", Rows));
        //        }

        //        return MatrixElements[column, row];
        //    }
        //    set {
        //        if (column < 0) {
        //            throw new ArgumentOutOfRangeException("column", "The index of the column must be greater than, or equal to, 0.");
        //        }

        //        if (column >= Columns) {
        //            throw new ArgumentOutOfRangeException("column", string.Format("The index of the column must be smaller than {0}.", Columns));
        //        }

        //        if (row < 0) {
        //            throw new ArgumentOutOfRangeException("row", "The index of the row must be greater than, or equal to, 0.");
        //        }

        //        if (row >= Rows) {
        //            throw new ArgumentOutOfRangeException("row", string.Format("The index of the column must be smaller than {0}.", Rows));
        //        }

        //        MatrixElements[column, row] = value;
        //    }
        //}
        #endregion

        #region enums
        ///// <summary>
        ///// The type of the elements of this Matrix.
        ///// </summary>
        ///// <remarks>
        ///// The default type is the Numeric one.
        ///// </remarks>
        //public enum ElementTypes { Numeric, BigNumeric, Matrix };
        #endregion

        #region delegates
        /// <summary>
        /// The multiplication method delegate. This will be used in the operations of the matrices.
        /// </summary>
        /// <param name="otherElement">
        /// The other element to multiply.
        /// </param>
        /// <returns>
        /// The element resulting from the multiplication of the two elements.
        /// </returns>
        public delegate T MultiplyElement(T left, T right);

        /// <summary>
        /// The addition method delegate. This will be used in the operations of the matrices.
        /// </summary>
        /// <param name="otherElement">
        /// The other element to add.
        /// </param>
        /// <returns>
        /// The element resulting from the addition of the two elements.
        /// </returns>
        public delegate T AddElement(T left, T right);

        /// <summary>
        /// A method that gets the sum identity for the specified element type.
        /// </summary>
        /// <returns>
        /// The sum identity for the type of elements.
        /// </returns>
        public delegate T GetElementSumIdentity();

        /// <summary>
        /// A method that gets the multiplication identity for the specified element type.
        /// </summary>
        /// <returns>
        /// The multiplication identity for the specified element type.
        /// </returns>
        public delegate T GetElementMultiplicationIdentity();
        #endregion

        #region constructors
        /// <summary>
        /// The default constructor of a Matrix.
        /// </summary>
        /// <remarks>
        /// The default number of rows and columns is 2.
        /// </remarks>
        public Matrix() : this(dimension: 2) { }

        ///// <summary>
        ///// A constructor of a Matrix instance.
        ///// </summary>
        ///// <param name="elementType">
        ///// The type of the elements contained in this Matrix.
        ///// </param>
        //public Matrix(ElementTypes elementType) : this(elementType, 2, 2) { }

        /// <summary>
        /// A constructor of a square Matrix instance.
        /// </summary>
        /// <param name="dimension">
        /// The dimension of the square Matrix.
        /// </param>
        public Matrix(int dimension) : this(dimension, dimension) { }

        //public Matrix(ElementTypes elementType, int dimension) : this(elementType, dimension, dimension) { }

        ///// <summary>
        ///// A constructor of a Matrix instance.
        ///// </summary>
        ///// <param name="columns">
        ///// The number of columns this Matrix should have.
        ///// </param>
        ///// <param name="rows">
        ///// The number of rows this Matrix should have.
        ///// </param>
        //public Matrix(int columns, int rows) : this(ElementTypes.Numeric, columns, rows) { }

        /// <summary>
        /// A constructor of a Matrix instance.
        /// </summary>
        /// <param name="rows">
        /// The number of rows the new instance must have.
        /// </param>
        /// <param name="columns">
        /// The number of columns the new instance must have.
        /// </param>
        public Matrix(int rows, int columns) {
            Rows = rows;
            Columns = columns;
            Elements = new T[Rows, Columns];
        }

        ///// <summary>
        ///// A constructor of a Matrix instance.
        ///// </summary>
        ///// <param name="elementType">
        ///// The type of the elements contained in this Matrix.
        ///// </param>
        ///// <param name="columns">
        ///// The number of columns this Matrix should have.
        ///// </param>
        ///// <param name="rows">
        ///// The number of rows this Matrix should have.
        ///// </param>
        //public Matrix(ElementTypes elementType, int columns, int rows) {
        //    if (columns <= 0) {
        //        throw new ArgumentOutOfRangeException("columns", "The number of columns can not be smaller than or equal to 0.");
        //    }

        //    if (rows <= 0) {
        //        throw new ArgumentOutOfRangeException("rows", "The number of rows can not be smaller than or equal to 0.");
        //    }

        //    ElementType = elementType;
        //    Columns = columns;
        //    Rows = rows;
        //    NumericElements = (elementType == ElementTypes.Numeric) 
        //                        ? new Complex[columns, rows] 
        //                        : null;
        //    BigNumericElements = (elementType == ElementTypes.BigNumeric) 
        //                            ? new BigInteger[columns, rows] 
        //                            : null;
        //    MatrixElements = (elementType == ElementTypes.Matrix)
        //                        ? new Matrix[columns, rows]
        //                        : null;
        //}
        #endregion

        #region methods
        #region addition methods
        public Matrix<T> Add(Matrix<T> toAdd) {
            if (this.Rows != toAdd.Rows) {
                throw new InvalidOperationException("The number of rows between the two matrices is not equal.");
            }

            if (this.Columns != toAdd.Columns) {
                throw new InvalidOperationException("The number of columns between the two matrices is not equal.");
            }

            if (!typeof(T).IsNumeric()) {
                throw new InvalidOperationException("This operation is not allowed for the given type.");
            }

            Matrix<T> result = new Matrix<T>(Rows, Columns);

            for (int i = 0; i < Rows; i++) {
                for(int j = 0; j < Columns; j++){
                    //result[i, j] = this[i, j].AddElement(toAdd[i, j]);
                }
            }

            return result;
        }

        ///// <summary>
        ///// A method that sums this Matrix to another.
        ///// </summary>
        ///// <param name="toAdd">
        ///// The Matrix to add to this one.
        ///// </param>
        ///// <returns>
        ///// The matrix result of the sum between the two matrices.
        ///// </returns>t
        //public Matrix<T> Add(Matrix<T> toAdd) {
        //    if (this.Rows != toAdd.Rows) {
        //        throw new ArgumentException("The number of rows between the two matrices is not equal.");
        //    }

        //    if (this.Columns != toAdd.Columns) {
        //        throw new ArgumentException("The number of columns between the two matrices is not equal.");
        //    }

        //    if (this.ElementType != toAdd.ElementType) {
        //        throw new ArgumentException("The two matrices' element types do not match. The two matrices must have the same element types.");
        //    }

        //    return ElementType == ElementTypes.Numeric
        //            ? AddNumeric(toAdd)
        //            : ElementType == ElementTypes.BigNumeric
        //                ? AddBigNumeric(toAdd)
        //                : AddMatrix(toAdd);
        //}

        ///// <summary>
        ///// A method that sums this Matrix to another asynchronously.
        ///// </summary>
        ///// <param name="toAdd">
        ///// The Matrix to add to this one.
        ///// </param>
        ///// <returns>
        ///// The matrix result of the sum between the two matrices.
        ///// </returns>
        //public Task<Matrix> AddAsync(Matrix toAdd) {
        //    return Task.Run(() => this.Add(toAdd));
        //}

        //private Matrix AddNumeric(Matrix toAdd) {
        //    Matrix result = new Matrix(ElementTypes.Numeric, Columns, Rows);

        //    lock (thisNumericElementsLoc) {
        //        for (int i = 0; i < Columns; i++) {
        //            for (int j = 0; j < Rows; j++) {
        //                result.NumericElements[i, j] = this.NumericElements[i, j] + toAdd.NumericElements[i, j];
        //            }
        //        }
        //    }

        //    return result;
        //}

        //private Matrix AddBigNumeric(Matrix toAdd) {
        //    Matrix result = new Matrix(ElementTypes.BigNumeric, Columns, Rows);

        //    lock (thisBigNumericElementsLoc) {
        //        for (int i = 0; i < Columns; i++) {
        //            for (int j = 0; j < Rows; j++) {
        //                result.BigNumericElements[i, j] = this.BigNumericElements[i, j] + toAdd.BigNumericElements[i, j];
        //            }
        //        }
        //    }

        //    return result;
        //}

        //private Matrix AddMatrix(Matrix toAdd) {
        //    Matrix result = new Matrix(ElementTypes.Matrix, Columns, Rows);

        //    lock (thisMatrixElementsLoc) {
        //        for (int i = 0; i < Columns; i++) {
        //            for (int j = 0; j < Rows; j++) {
        //                result.MatrixElements[i, j] = this.MatrixElements[i, j] + toAdd.MatrixElements[i, j];
        //            }
        //        }
        //    }

        //    return result;
        //}
        #endregion

        #region multiplication methods
        ///// <summary>
        ///// A method that multiplies this Matrix with another Matrix.
        ///// </summary>
        ///// <param name="multiplicator">
        ///// The other Matrix.
        ///// </param>
        ///// <returns>
        ///// The Matrix resulting from the multiplication between the 
        ///// two matrices.
        ///// </returns>
        //public Matrix Multiply(Matrix multiplicator) {
        //    if (this.Columns != multiplicator.Rows) {
        //        throw new ArgumentException(string.Format(
        //            "{0} {1}",
        //            "The number of columns in this Matrix is different from the number of rows in the multiplicator Matrix.",
        //            "They must be equal."));
        //    }

        //    if (this.ElementType != multiplicator.ElementType) {
        //        throw new ArgumentException("The matrices must contain the same type of elements");
        //    }

        //    return (this.ElementType == ElementTypes.Numeric)
        //            ? this.MultiplyWithNumeric(multiplicator)
        //            : (this.ElementType == ElementTypes.BigNumeric)
        //                ? this.MultiplyWithBigNumeric(multiplicator)
        //                : this.MultiplyWithMatrix(multiplicator);
        //}

        ///// <summary>
        ///// A method that multiplies this Matrix with a scalar.
        ///// </summary>
        ///// <param name="scalarMultiplicator">
        ///// The scalar by which this Matrix will be multiplied.
        ///// </param>
        ///// <returns>
        ///// The Matrix resulting from the multiplication between 
        ///// this Matrix and the specified scalar.
        ///// </returns>
        //public Matrix MultiplyNumeric(Complex scalarMultiplicator) {
        //    if (this.ElementType != ElementTypes.Numeric) {
        //        throw new ArgumentException("The elements of this Matrix must be numerics.");
        //    }

        //    Matrix result = new Matrix(ElementTypes.Numeric, Columns, Rows);

        //    lock (thisNumericElementsLoc) {
        //        for (int i = 0; i < Columns; i++) {
        //            for (int j = 0; j < Rows; j++) {
        //                result.NumericElements[i, j] = this.NumericElements[i, j] * scalarMultiplicator;
        //            }
        //        }
        //    }

        //    return result;
        //}

        //public Matrix MultiplyBigNumeric(BigInteger scalarMultiplicator) {
        //    if (this.ElementType != ElementTypes.BigNumeric) {
        //        throw new ArgumentException("The elements of this Matrix must be big numerics.");
        //    }

        //    Matrix result = new Matrix(ElementTypes.BigNumeric, Columns, Rows);

        //    lock (thisBigNumericElementsLoc) {
        //        for (int i = 0; i < Columns; i++) {
        //            for (int j = 0; j < Rows; j++) {
        //                result.BigNumericElements[i, j] = this.BigNumericElements[i, j] * scalarMultiplicator;
        //            }
        //        }
        //    }

        //    return result;
        //}

        //public Matrix MultiplyMatrix(Matrix scalarMultiplicator) {
        //    if (this.ElementType != ElementTypes.Matrix) {
        //        throw new ArgumentException("The elements of this Matrix must be other matrices.");
        //    }

        //    Matrix result = new Matrix(ElementTypes.Matrix, Columns, Rows);

        //    lock (thisMatrixElementsLoc) {
        //        for (int i = 0; i < Columns; i++) {
        //            for (int j = 0; j < Rows; j++) {
        //                result.MatrixElements[i, j] = this.MatrixElements[i, j] * scalarMultiplicator;
        //            }
        //        }
        //    }

        //    return result;
        //}

        //public Task<Matrix> MultiplyAsync(Matrix multiplicator) {
        //    return Task.Run(() => this.Multiply(multiplicator));
        //}

        //public Task<Matrix> MultiplyNumericAsync(Complex scalarMultiplicator) {
        //    return Task.Run(() => this.MultiplyNumeric(scalarMultiplicator));
        //}

        //public Task<Matrix> MultiplyBigNumericAsync(BigInteger scalarMultiplicator) {
        //    return Task.Run(() => this.MultiplyBigNumeric(scalarMultiplicator));
        //}

        ///// <summary>
        ///// A method that multiplies this Matrix with a scalar asynchronously.
        ///// </summary>
        ///// <param name="scalarMultiplicator">
        ///// The scalar by which this Matrix will be multiplied.
        ///// </param>
        ///// <returns>
        ///// The Matrix resulting from the multiplication between 
        ///// this Matrix and the specified scalar.
        ///// </returns>
        //public Task<Matrix> MultiplyMatrixAsync(Matrix scalarMultiplicator) {
        //    return Task.Run(() => this.MultiplyMatrix(scalarMultiplicator));
        //}

        //private Matrix MultiplyWithNumeric(Matrix multiplicator) {
        //    Matrix result = new Matrix(elementType: ElementTypes.Numeric, columns: multiplicator.Columns, rows: this.Rows);

        //    lock (thisNumericElementsLoc) {
        //        for (int j = 0; j < result.Rows; j++) {
        //            for (int i = 0; i < result.Columns; i++) {
        //                result.NumericElements[i, j] = Multiply(row: this.GetNumericRowNoLock(j), column: multiplicator.GetNumericColumnNoLock(i));
        //            }
        //        }
        //    }

        //    return result;
        //}

        //private Matrix MultiplyWithBigNumeric(Matrix multiplicator) {
        //    Matrix result = new Matrix(elementType: ElementTypes.BigNumeric, columns: multiplicator.Columns, rows: this.Rows);

        //    lock (thisBigNumericElementsLoc) {
        //        for (int j = 0; j < result.Rows; j++) {
        //            for (int i = 0; i < result.Columns; i++) {
        //                result.BigNumericElements[i, j] = Multiply(row: this.GetBigNumericRowNoLock(j), column: multiplicator.GetBigNumericColumnNoLock(i));
        //            }
        //        }
        //    }

        //    return result;
        //}

        //private Matrix MultiplyWithMatrix(Matrix multiplicator) {
        //    Matrix result = new Matrix(elementType: ElementTypes.Matrix, columns: multiplicator.Columns, rows: this.Rows);

        //    lock (thisMatrixElementsLoc) {
        //        for (int j = 0; j < result.Rows; j++) {
        //            for (int i = 0; i < result.Columns; i++) {
        //                result.MatrixElements[i, j] = Multiply(row: this.GetMatrixRowNoLock(j), column: multiplicator.GetMatrixColumnNoLock(i));
        //            }
        //        }
        //    }

        //    return result;
        //}

        ///// <summary>
        ///// A method that multiplies a Matrix row with another Matrix's column.
        ///// </summary>
        ///// <param name="row">
        ///// The row to multiply to the specified column.
        ///// </param>
        ///// <param name="column">
        ///// The column to multiply to the specified row.
        ///// </param>
        ///// <returns>
        ///// The result of the multiplication.
        ///// </returns>
        //private static Complex Multiply(Complex[] row, Complex[] column) {
        //    Complex result = 0;

        //    for (int i = 0; i < row.Length; i++) {
        //        result += (row[i] * column[i]);
        //    }

        //    return result;
        //}

        //private static BigInteger Multiply(BigInteger[] row, BigInteger[] column) {
        //    BigInteger result = 0;

        //    for (int i = 0; i < row.Length; i++) {
        //        result += (row[i] * column[i]);
        //    }

        //    return result;
        //}

        //private static Matrix Multiply(Matrix[] row, Matrix[] column) {
        //    Matrix result = new Matrix(row[0].ElementType, row[0].Columns);

        //    for (int i = 0; i < row.Length; i++) {
        //        if (!row[i].IsSquare()) {
        //            throw new ArgumentException("The multiplication between non square matrices has not been implemented yet.");
        //        }

        //        result += (row[i] * column[i]);
        //    }

        //    return result;
        //}
        #endregion

        #region transpose
        ///// <summary>
        ///// A method that builds the transposed Matrix of this Matrix.
        ///// </summary>
        ///// <returns>
        ///// The transposed Matrix of this Matrix instance.
        ///// </returns>
        //public Matrix Transpose() {
        //    return (this.ElementType == ElementTypes.Matrix)
        //            ? TransposeMatrix()
        //            : (this.ElementType == ElementTypes.Numeric)
        //                ? TransposeNumeric()
        //                : TransposeBigNumeric();
        //}

        //public Task<Matrix> TransposeAsync() {
        //    return Task.Run(() => this.Transpose());
        //}

        //private Matrix TransposeNumeric() {
        //    Matrix result = new Matrix(elementType: ElementTypes.Numeric, columns: Rows, rows: Columns);

        //    lock (thisNumericElementsLoc) {
        //        for (int i = 0; i < Columns; i++) {
        //            for (int j = 0; j < Rows; j++) {
        //                result.NumericElements[j, i] = this.NumericElements[i, j];
        //            }
        //        }
        //    }

        //    return result;
        //}

        //private Matrix TransposeBigNumeric() {
        //    Matrix result = new Matrix(elementType: ElementTypes.BigNumeric, columns: Rows, rows: Columns);

        //    lock (thisBigNumericElementsLoc) {
        //        for (int i = 0; i < Columns; i++) {
        //            for (int j = 0; j < Rows; j++) {
        //                result.BigNumericElements[j, i] = this.BigNumericElements[i, j];
        //            }
        //        }
        //    }

        //    return result;
        //}

        //private Matrix TransposeMatrix() {
        //    Matrix result = new Matrix(elementType: ElementTypes.Matrix, columns: Rows, rows: Columns);

        //    lock (thisMatrixElementsLoc) {
        //        for (int i = 0; i < Columns; i++) {
        //            for (int j = 0; j < Rows; j++) {
        //                result.MatrixElements[j, i] = this.MatrixElements[i, j];
        //            }
        //        }
        //    }

        //    return result;
        //}
        #endregion

        #region get row
        ///// <summary>
        ///// Get the row with the specified index.
        ///// </summary>
        ///// <param name="index">
        ///// The index of the desired row.
        ///// </param>
        ///// <returns>
        ///// The row with the specified index.
        ///// </returns>
        ///// <remarks>
        ///// The index is 0-based.
        ///// </remarks>
        //public Complex[] GetNumericRow(int index) {
        //    if (index < 0) {
        //        throw new ArgumentOutOfRangeException("index", "The indicated index cannot be smaller than 0.");
        //    }

        //    if (index >= Rows) {
        //        throw new ArgumentOutOfRangeException("index", string.Format("The indicated index cannot be greater than {0}.", Rows - 1));
        //    }

        //    Complex[] result; 

        //    lock (thisNumericElementsLoc) {
        //        result = GetNumericColumnNoLock(index);
        //    }

        //    return result;
        //}

        //public BigInteger[] GetBigNumericRow(int index) {
        //    if (index < 0) {
        //        throw new ArgumentOutOfRangeException("index", "The indicated index cannot be smaller than 0.");
        //    }

        //    if (index >= Rows) {
        //        throw new ArgumentOutOfRangeException("index", string.Format("The indicated index cannot be greater than {0}.", Rows - 1));
        //    }

        //    BigInteger[] result; 

        //    lock (thisBigNumericElementsLoc) {
        //        result = GetBigNumericColumnNoLock(index);
        //    }

        //    return result;
        //}

        //public Matrix[] GetMatrixRow(int index) {
        //    if (index < 0) {
        //        throw new ArgumentOutOfRangeException("index", "The indicated index cannot be smaller than 0.");
        //    }

        //    if (index >= Rows) {
        //        throw new ArgumentOutOfRangeException("index", string.Format("The indicated index cannot be greater than {0}.", Rows - 1));
        //    }

        //    Matrix[] result;

        //    lock (thisMatrixElementsLoc) {
        //        result = GetMatrixColumnNoLock(index);
        //    }

        //    return result;
        //}

        //public Task<Complex[]> GetNumericRowAsync(int index) {
        //    return Task.Run(() => this.GetNumericRow(index));
        //}

        //public Task<BigInteger[]> GetBigNumericRowAsync(int index) {
        //    return Task.Run(() => this.GetBigNumericRow(index));
        //}

        //public Task<Matrix[]> GetMatrixRowAsync(int index) {
        //    return Task.Run(() => this.GetMatrixRow(index));
        //}

        //private Complex[] GetNumericRowNoLock(int index) {
        //    Complex[] result = new Complex[Columns];

        //    for (int i = 0; i < Columns; i++) {
        //        result[i] = NumericElements[i, index];
        //    }

        //    return result;
        //}

        //private BigInteger[] GetBigNumericRowNoLock(int index) {
        //    BigInteger[] result = new BigInteger[Columns];

        //    for (int i = 0; i < Columns; i++) {
        //        result[i] = BigNumericElements[i, index];
        //    }

        //    return result;
        //}

        //private Matrix[] GetMatrixRowNoLock(int index) {
        //    Matrix[] result = new Matrix[Columns];

        //    for (int i = 0; i < Columns; i++) {
        //        result[i] = MatrixElements[i, index];
        //    }

        //    return result;
        //}
        #endregion

        #region get column
        ///// <summary>
        ///// Get the column with the specified index.
        ///// </summary>
        ///// <param name="index">
        ///// The index of the desired column.
        ///// </param>
        ///// <returns>
        ///// The column with the specified index.
        ///// </returns>
        ///// <remarks>
        ///// The index is 0-based.
        ///// </remarks>
        //public Complex[] GetNumericColumn(int index) {
        //    if (index < 0) {
        //        throw new ArgumentOutOfRangeException("index", "The indicated index cannot be smaller than 0.");
        //    }

        //    if (index >= Columns) {
        //        throw new ArgumentOutOfRangeException("index", string.Format("The indicated index cannot be greater than {0}.", Rows - 1));
        //    }

        //    Complex[] result = new Complex[Rows];

        //    lock (thisNumericElementsLoc) {
        //        result = GetNumericColumnNoLock(index);
        //    }

        //    return result;
        //}

        //public BigInteger[] GetBigNumericColumn(int index) {
        //    if (index < 0) {
        //        throw new ArgumentOutOfRangeException("index", "The indicated index cannot be smaller than 0.");
        //    }

        //    if (index >= Columns) {
        //        throw new ArgumentOutOfRangeException("index", string.Format("The indicated index cannot be greater than {0}.", Rows - 1));
        //    }

        //    BigInteger[] result = new BigInteger[Rows];

        //    lock (thisBigNumericElementsLoc) {
        //        result = GetBigNumericColumnNoLock(index);
        //    }

        //    return result;
        //}

        //public Matrix[] GetMatrixColumn(int index) {
        //    if (index < 0) {
        //        throw new ArgumentOutOfRangeException("index", "The indicated index cannot be smaller than 0.");
        //    }

        //    if (index >= Columns) {
        //        throw new ArgumentOutOfRangeException("index", string.Format("The indicated index cannot be greater than {0}.", Rows - 1));
        //    }

        //    Matrix[] result = new Matrix[Rows];

        //    lock (thisMatrixElementsLoc) {
        //        result = GetMatrixColumnNoLock(index);
        //    }

        //    return result;
        //}

        //public Task<Complex[]> GetNumericColumnAsync(int index) {
        //    return Task.Run(() => this.GetNumericColumn(index));
        //}

        //public Task<BigInteger[]> GetBigNumericColumnAsync(int index) {
        //    return Task.Run(() => this.GetBigNumericColumn(index));
        //}

        //public Task<Matrix[]> GetMatrixColumnAsync(int index) {
        //    return Task.Run(() => this.GetMatrixColumn(index));
        //}

        //private Complex[] GetNumericColumnNoLock(int index) {
        //    Complex[] result = new Complex[Rows];

        //    for (int i = 0; i < Rows; i++) {
        //        result[i] = NumericElements[index, i];
        //    }

        //    return result;
        //}

        //private BigInteger[] GetBigNumericColumnNoLock(int index) {
        //    BigInteger[] result = new BigInteger[Rows];

        //    for (int i = 0; i < Rows; i++) {
        //        result[i] = BigNumericElements[index, i];
        //    }

        //    return result;
        //}

        //private Matrix[] GetMatrixColumnNoLock(int index) {
        //    Matrix[] result = new Matrix[Rows];

        //    for (int i = 0; i < Rows; i++) {
        //        result[i] = MatrixElements[index, i];
        //    }

        //    return result;
        //}
        #endregion

        #region get trace
        ///// <summary>
        ///// A method that calculates the trace of this Matrix.
        ///// </summary>
        ///// <returns>
        ///// The trace of this square Matrix.
        ///// </returns>
        //public Complex GetNumericTrace() {
        //    if (!this.IsSquare()) {
        //        throw new ArgumentException("This Matrix must be a square Matrix.");
        //    }

        //    if (this.ElementType != ElementTypes.Numeric) {
        //        throw new ArgumentException("This Matrix must contain numeric elements.");
        //    }

        //    Complex result = 0;

        //    lock (thisNumericElementsLoc) {
        //        for (int i = 0; i < Columns; i++) {
        //            result += NumericElements[i, i];
        //        }
        //    }

        //    return result;
        //}

        ///// <summary>
        ///// A method that calculates the trace of this Matrix.
        ///// </summary>
        ///// <returns>
        ///// The trace of this square Matrix.
        ///// </returns>
        //public BigInteger GetBigNumericTrace() {
        //    if (!this.IsSquare()) {
        //        throw new ArgumentException("This Matrix must be a square Matrix.");
        //    }

        //    if (this.ElementType != ElementTypes.BigNumeric) {
        //        throw new ArgumentException("This Matrix must contain big numerics.");
        //    }

        //    BigInteger result = 0;

        //    lock (thisBigNumericElementsLoc) {
        //        for (int i = 0; i < Columns; i++) {
        //            result += BigNumericElements[i, i];
        //        }
        //    }

        //    return result;
        //}

        ///// <summary>
        ///// A method that calculates the trace of this Matrix.
        ///// </summary>
        ///// <returns>
        ///// The trace of this square Matrix.
        ///// </returns>
        //public Matrix GetMatrixTrace() {
        //    if (!this.IsSquare()) {
        //        throw new ArgumentException("This Matrix must be a square Matrix.");
        //    }

        //    if (this.ElementType != ElementTypes.Matrix) {
        //        throw new ArgumentException("The elements of this Matrix must be other matrices.");
        //    }

        //    Matrix result;

        //    lock (thisMatrixElementsLoc) {
        //        result = MatrixElements[0, 0];

        //        for (int i = 1; i < Columns; i++) {
        //            result += MatrixElements[i, i];
        //        }
        //    }

        //    return result;
        //}

        //public Task<Complex> GetNumericTraceAsync() {
        //    return Task.Run(() => this.GetNumericTrace());
        //}

        //public Task<BigInteger> GetBigNumericTraceAsync() {
        //    return Task.Run(() => this.GetBigNumericTrace());
        //}

        //public Task<Matrix> GetMatrixTraceAsync() {
        //    return Task.Run(() => this.GetMatrixTrace());
        //}
        #endregion

        #region get determinant
        ///// <summary>
        ///// A method that calculates and returns the determinant of this square Matrix.
        ///// </summary>
        ///// <returns>
        ///// The determinant of this square Matrix.
        ///// </returns>
        //public Complex GetNumericDeterminant() {
        //    if (!IsSquare()) {
        //        throw new ArgumentException("This Matrix must be a square one.");
        //    }

        //    Complex topDownResult = 0;
        //    Complex bottomUpResult = 0;
        //    Complex tempResult = 1;

        //    lock (thisNumericElementsLoc) {
        //        // let's calculate the sum of the top-down diagonals
        //        for (int i = 0; i < Rows; i++) {
        //            tempResult = 1;

        //            for (int j = 0; j < Rows; j++) {
        //                tempResult *= NumericElements[(i + j) % Rows, j]; // i + (j % Rows)];
        //            }

        //            topDownResult += tempResult;
        //        }

        //        // now let's calculate the sum of the bottom-up diagonals
        //        for (int i = 0; i < Rows; i++) {
        //            tempResult = 1;

        //            for (int j = 0; j < Rows; j++) {
        //                tempResult *= NumericElements[(i + j) % Rows, (Rows - j - 1)];
        //            }

        //            bottomUpResult += tempResult;
        //        }
        //    }

        //    return topDownResult - bottomUpResult;
        //}

        ///// <summary>
        ///// A method that calculates and returns the determinant of this square Matrix.
        ///// </summary>
        ///// <returns>
        ///// The determinant of this square Matrix.
        ///// </returns>
        //public BigInteger GetBigNumericDeterminant() {
        //    if (!IsSquare()) {
        //        throw new ArgumentException("This Matrix must be a square one.");
        //    }

        //    BigInteger topDownResult = 0;
        //    BigInteger bottomUpResult = 0;
        //    BigInteger tempResult = 1;

        //    lock (thisBigNumericElementsLoc) {
        //        // let's calculate the sum of the top-down diagonals
        //        for (int i = 0; i < Rows; i++) {
        //            tempResult = 1;

        //            for (int j = 0; j < Rows; j++) {
        //                tempResult *= BigNumericElements[(i + j) % Rows, j]; // i + (j % Rows)];
        //            }

        //            topDownResult += tempResult;
        //        }

        //        // now let's calculate the sum of the bottom-up diagonals
        //        for (int i = 0; i < Rows; i++) {
        //            tempResult = 1;

        //            for (int j = 0; j < Rows; j++) {
        //                tempResult *= BigNumericElements[(i + j) % Rows, (Rows - j - 1)];
        //            }

        //            bottomUpResult += tempResult;
        //        }
        //    }

        //    return topDownResult - bottomUpResult;
        //}

        /////// <summary>
        /////// A method that calculates and returns the determinant of this square Matrix.
        /////// </summary>
        /////// <returns>
        /////// The determinant of this square Matrix.
        /////// </returns>
        ////public Matrix GetMatrixDeterminant() {
        ////    if (!IsSquare()) {
        ////        throw new ArgumentException("This Matrix must be a square one.");
        ////    }

        //    //Matrix topDownResult = new Matrix(this.MatrixElements[0, 0].ElementType, this.MatrixElements[0, 0].Columns, this.MatrixElements[0, 0].Rows);
        //    //Matrix bottomUpResult;// = 0;
        //    //Matrix tempResult;// = 1;

        //    //lock (thisBigNumericElementsLoc) {
        //    //    // let's calculate the sum of the top-down diagonals
        //    //    for (int i = 0; i < Rows; i++) {
        //    //        tempResult = 1;

        //    //        for (int j = 0; j < Rows; j++) {
        //    //            tempResult *= BigNumericElements[(i + j) % Rows, j]; // i + (j % Rows)];
        //    //        }

        //    //        topDownResult += tempResult;
        //    //    }

        //    //    // now let's calculate the sum of the bottom-up diagonals
        //    //    for (int i = 0; i < Rows; i++) {
        //    //        tempResult = 1;

        //    //        for (int j = 0; j < Rows; j++) {
        //    //            tempResult *= BigNumericElements[(i + j) % Rows, (Rows - j - 1)];
        //    //        }

        //    //        bottomUpResult += tempResult;
        //    //    }
        //    //}

        ////    return null;// topDownResult - bottomUpResult;
        ////}

        //public Task<Complex> GetNumericDeterminantAsync() {
        //    return Task.Run(() => this.GetNumericDeterminant());
        //}

        //public Task<BigInteger> GetBigNumericDeterminantAsync() {
        //    return Task.Run(() => this.GetBigNumericDeterminant());
        //}
        #endregion

        #region equals
        ///// <summary>
        ///// A method that checks if a given object is equal to this Matrix.
        ///// </summary>
        ///// <param name="obj">
        ///// The object to check for equality with this Matrix.
        ///// </param>
        ///// <returns>
        ///// TRUE if obj is equal to this Matrix, FALSE otherwise.
        ///// </returns>
        //public override bool Equals(object obj) {
        //    return (obj is Matrix) && this.Equals((Matrix)obj);
        //}

        ///// <summary>
        ///// A method that checks if this Matrix is equal to another Matrix.
        ///// </summary>
        ///// <param name="other">
        ///// The other Matrix to check for equality.
        ///// </param>
        ///// <returns>
        ///// TRUE if the two matrices are equal, FALSE otherwise.
        ///// </returns>
        //public bool Equals(Matrix other) {
        //    return (this.ElementType == other.ElementType)
        //        && (this.ElementType == ElementTypes.Numeric)
        //            ? this.EqualsNumeric(other)
        //            : (this.ElementType == ElementTypes.Matrix)
        //                ? this.EqualsMatrix(other)
        //                : this.EqualsBigNumeric(other);
        //}

        //public Task<bool> EqualsAsync(object obj) {
        //    return Task.Run(() => this.Equals(obj));
        //}

        //public Task<bool> EqualsAsync(Matrix other) {
        //    return Task.Run(() => this.Equals(other));
        //}

        //private bool EqualsNumeric(Matrix other) {
        //    bool result = false;

        //    if (this.Columns == other.Columns && this.Rows == other.Rows) {
        //        result = true;

        //        lock (thisNumericElementsLoc) {
        //            for (int i = 0; result && i < Columns; i++) {
        //                for (int j = 0; result && j < Rows; j++) {
        //                    result = result && this.NumericElements[i, j] == other.NumericElements[i, j];
        //                }
        //            }
        //        }
        //    }

        //    return result;
        //}

        //private bool EqualsBigNumeric(Matrix other) {
        //    bool result = false;

        //    if (this.Columns == other.Columns && this.Rows == other.Rows) {
        //        result = true;

        //        lock (thisBigNumericElementsLoc) {
        //            for (int i = 0; result && i < Columns; i++) {
        //                for (int j = 0; result && j < Rows; j++) {
        //                    result = result && this.BigNumericElements[i, j] == other.BigNumericElements[i, j];
        //                }
        //            }
        //        }
        //    }

        //    return result;
        //}

        //private bool EqualsMatrix(Matrix other) {
        //    bool result = false;

        //    if (this.Columns == other.Columns && this.Rows == other.Rows) {
        //        result = true;

        //        lock (thisMatrixElementsLoc) {
        //            for (int i = 0; result && i < Columns; i++) {
        //                for (int j = 0; result && j < Rows; j++) {
        //                    result = result && this.MatrixElements[i, j] == other.MatrixElements[i, j];
        //                }
        //            }
        //        }
        //    }

        //    return result;
        //}
        #endregion

        #region is square
        ///// <summary>
        ///// A method that checks if this Matrix is a square one or not.
        ///// </summary>
        ///// <returns>
        ///// TRUE if this Matrix is a square one, FALSE otherwise.
        ///// </returns>
        //public bool IsSquare() {
        //    return Rows == Columns;
        //}

        //public Task<bool> IsSquareAsync() {
        //    return Task.Run(() => this.IsSquare());
        //}
        #endregion

        #region is symmetric
        ///// <summary>
        ///// A method that checks if this Matrix is a symmetric one or not.
        ///// </summary>
        ///// <returns>
        ///// TRUE if this Matrix is a square one, FALSE otherwise.
        ///// </returns>
        //public bool IsSymmetric() {
        //    bool result = false;

        //    // only a square matrix can be symmetric
        //    if (IsSquare()) {
        //        result = true;
        //        //object[,] elements = NumericElements ?? BigNumericElements ?? MatrixElements;

        //        lock ((this.ElementType == ElementTypes.Numeric)
        //                ? thisNumericElementsLoc
        //                : (this.ElementType == ElementTypes.Matrix)
        //                    ? thisMatrixElementsLoc
        //                    : thisBigNumericElementsLoc) {
        //            // we want to exit the cycle as soon as we find a mismatching element
        //            for (int i = 0; result && i < Rows; i++) {
        //                // we are checking only half of the elements in the Matrix
        //                for (int j = i; result && j < Rows; j++) {
        //                    //result = result && (Elements[i, j] == Elements[j, i]);
        //                    result = result && (this.ElementType == ElementTypes.Numeric)
        //                                        ? NumericElements[i, j] == NumericElements[j, i]
        //                                        : (this.ElementType == ElementTypes.Matrix)
        //                                            ? MatrixElements[i, j] == MatrixElements[j, i]
        //                                            : BigNumericElements[i, j] == BigNumericElements[j, i];
        //                }
        //            }
        //        }
        //    }

        //    return result;
        //}

        //public Task<bool> IsSymmetricAsync() {
        //    return Task.Run(() => this.IsSymmetric());
        //}
        #endregion

        #region to string
        ///// <summary>
        ///// Get the string representation of the Matrix.
        ///// </summary>
        ///// <returns>
        ///// The string representation of the Matrix.
        ///// </returns>
        //public override string ToString() {
        //    string result = "";

        //    lock ((this.ElementType == ElementTypes.Matrix)
        //            ? thisMatrixElementsLoc
        //            : (this.ElementType == ElementTypes.Numeric)
        //                ? thisNumericElementsLoc
        //                : thisBigNumericElementsLoc) {
        //        for (int i = 0; i < Rows; i++) {
        //            for (int j = 0; j < Columns; j++) {
        //                if (this.ElementType == ElementTypes.BigNumeric) {
        //                    result += string.Format("{0}\t", this.BigNumericElements[j, i]);
        //                } else if (this.ElementType == ElementTypes.Numeric) {
        //                    result += string.Format("{0}\t", this.NumericElements[j, i]);
        //                } else if (this.ElementType == ElementTypes.Matrix) {
        //                    result += string.Format("{0}\t", this.MatrixElements[j, i]);
        //                }
        //            }

        //            result += "\n";
        //        }
        //    }

        //    return result;
        //}

        //public Task<string> ToStringAsync() {
        //    return Task.Run(() => this.ToString());
        //}
        #endregion

        #region get identity
        ///// <summary>
        ///// A method that gets the Identity Matrix of a given size.
        ///// </summary>
        ///// <param name="size">
        ///// The desired size of the Identity Matrix.
        ///// </param>
        ///// <returns>
        ///// The Identity Matrix of a given size.
        ///// </returns>
        //public static Matrix GetNumericMultiplicationIdentity(int size = 2) {
        //    if (size <= 1) {
        //        throw new ArgumentOutOfRangeException("size", "The size cannot be smaller than or equal to 1.");
        //    }

        //    Matrix result = new Matrix(elementType: ElementTypes.Numeric, columns: size, rows: size);

        //    for (int i = 0; i < size; i++) {
        //        for (int j = 0; j < size; j++) {
        //            result.NumericElements[i, j] = (i == j) ? 1 : 0;
        //        }
        //    }

        //    return result;
        //}

        //public static Matrix GetBigNumericMultiplicationIdentity(int size = 2) {
        //    if (size <= 1) {
        //        throw new ArgumentOutOfRangeException("size", "The size cannot be smaller than or equal to 1.");
        //    }

        //    Matrix result = new Matrix(elementType: ElementTypes.BigNumeric, columns: size, rows: size);

        //    for (int i = 0; i < size; i++) {
        //        for (int j = 0; j < size; j++) {
        //            result.BigNumericElements[i, j] = (i == j) ? 1 : 0;
        //        }
        //    }

        //    return result;
        //}

        //public static Matrix GetNumericAdditionIdentity(int size = 2) {
        //    return GetNumericAdditionIdentity(columns: size, rows: size);
        //}

        //public static Matrix GetNumericAdditionIdentity(int columns, int rows) {
        //    if (columns <= 0) {
        //        throw new ArgumentOutOfRangeException("columns", "The number of columns must be greater than 0.");
        //    }

        //    if (rows <= 0) {
        //        throw new ArgumentOutOfRangeException("rows", "The number of rows must be greater than 0.");
        //    }

        //    if (columns == 1 && rows == 1) {
        //        throw new ArgumentOutOfRangeException("The number of columns and rows cannot be both 1. One of the two must be greater than 1.", new Exception());
        //    }

        //    Matrix result = new Matrix(elementType: ElementTypes.Numeric, columns: columns, rows: rows);

        //    for (int i = 0; i < columns; i++) {
        //        for (int j = 0; j < rows; j++) {
        //            result.NumericElements[i, j] = 0;
        //        }
        //    }

        //    return result;
        //}

        //public static Matrix GetBigNumericAdditionIdentity(int size = 2) {
        //    return GetBigNumericAdditionIdentity(columns: size, rows: size);
        //}

        //public static Matrix GetBigNumericAdditionIdentity(int columns, int rows) {
        //    if (columns <= 0) {
        //        throw new ArgumentOutOfRangeException("columns", "The number of columns must be greater than 0.");
        //    }

        //    if (rows <= 0) {
        //        throw new ArgumentOutOfRangeException("rows", "The number of rows must be greater than 0.");
        //    }

        //    if (columns == 1 && rows == 1) {
        //        throw new ArgumentOutOfRangeException("The number of columns and rows cannot be 1 for both.", new Exception());
        //    }

        //    Matrix result = new Matrix(ElementTypes.BigNumeric, columns, rows);

        //    for (int i = 0; i < columns; i++) {
        //        for (int j = 0; j < rows; j++) {
        //            result.BigNumericElements[i, j] = 0;
        //        }
        //    }

        //    return result;
        //}

        //public static Task<Matrix> GetNumericMultiplicationIdentityAsync(int size = 2) {
        //    return Task.Run(() => GetNumericMultiplicationIdentity(size));
        //}

        //public static Task<Matrix> GetBigNumericMultiplicationIdentityAsync(int size = 2) {
        //    return Task.Run(() => GetBigNumericMultiplicationIdentity(size));
        //}

        //public static Task<Matrix> GetNumericAdditionIdentityAsync(int size = 2) {
        //    return GetNumericAdditionIdentityAsync(size, size);
        //}

        //public static Task<Matrix> GetNumericAdditionIdentityAsync(int columns, int rows) {
        //    return Task.Run(() => GetNumericAdditionIdentity(columns, rows));
        //}

        //public static Task<Matrix> GetBigNumericAdditionIdentityAsync(int size = 2) {
        //    return GetBigNumericAdditionIdentityAsync(size, size);
        //}

        //public static Task<Matrix> GetBigNumericAdditionIdentityAsync(int columns, int rows) {
        //    return Task.Run(() => GetBigNumericAdditionIdentity(columns, rows));
        //}
        #endregion

        #region get hash code
        //public override int GetHashCode() {
        //    int hashCode = ElementType.GetHashCode();

        //    if (ElementType == ElementTypes.BigNumeric) {
        //        lock (thisBigNumericElementsLoc) {
        //            for (int i = 0; i < Columns; i++) {
        //                for (int j = 0; j < Rows; j++) {
        //                    hashCode = hashCode ^ BigNumericElements[i, j].GetHashCode();
        //                }
        //            }
        //        }
        //    } else if (ElementType == ElementTypes.Numeric) {
        //        lock (thisNumericElementsLoc) {
        //            for (int i = 0; i < Columns; i++) {
        //                for (int j = 0; j < Rows; j++) {
        //                    hashCode = hashCode ^ NumericElements[i, j].GetHashCode();
        //                }
        //            }
        //        }
        //    }

        //    return hashCode;
        //}

        //public Task<int> GetHashCodeAsync() {
        //    return Task.Run(() => GetHashCode());
        //}
        #endregion

        #region utility methods
        private static MultiplyElement GetMultiplyElement() {
            MultiplyElement me = null;

            if (typeof(T) == typeof(byte)) {
                me = ((left, right) => (left.CastTo<byte>() * right.CastTo<byte>()).CastTo<T>());
            } else if (typeof(T) == typeof(decimal)) {
                me = ((left, right) => (left.CastTo<decimal>() * right.CastTo<decimal>()).CastTo<T>());
            } else if (typeof(T) == typeof(double)) {
                me = ((left, right) => (left.CastTo<double>() * right.CastTo<double>()).CastTo<T>());
            } else if (typeof(T) == typeof(float)) {
                me = ((left, right) => (left.CastTo<float>() * right.CastTo<float>()).CastTo<T>());
            } else if (typeof(T) == typeof(int)) {
                me = ((left, right) => (left.CastTo<int>() * right.CastTo<int>()).CastTo<T>());
            } else if (typeof(T) == typeof(long)) {
                me = ((left, right) => (left.CastTo<long>() * right.CastTo<long>()).CastTo<T>());
            } else if (typeof(T) == typeof(sbyte)) {
                me = ((left, right) => (left.CastTo<sbyte>() * right.CastTo<sbyte>()).CastTo<T>());
            } else if (typeof(T) == typeof(short)) {
                me = ((left, right) => (left.CastTo<short>() * right.CastTo<short>()).CastTo<T>());
            } else if (typeof(T) == typeof(uint)) {
                me = ((left, right) => (left.CastTo<uint>() * right.CastTo<uint>()).CastTo<T>());
            } else if (typeof(T) == typeof(ulong)) {
                me = ((left, right) => (left.CastTo<ulong>() * right.CastTo<ulong>()).CastTo<T>());
            } else if (typeof(T) == typeof(ushort)) {
                me = ((left, right) => (left.CastTo<ushort>() * right.CastTo<ushort>()).CastTo<T>());
            } else if (typeof(T) == typeof(Complex)) {
                me = ((left, right) => (left.CastTo<Complex>() * right.CastTo<Complex>()).CastTo<T>());
            } else if (typeof(T) == typeof(BigInteger)) {
                me = ((left, right) => (left.CastTo<BigInteger>() * right.CastTo<BigInteger>()).CastTo<T>());
            }

            return me;
        }

        private static AddElement GetAddElement() {
            AddElement ae = null;

            if (typeof(T) == typeof(byte)) {
                ae = ((left, right) => (left.CastTo<byte>() + right.CastTo<byte>()).CastTo<T>());
            } else if (typeof(T) == typeof(decimal)) {
                ae = ((left, right) => (left.CastTo<decimal>() + right.CastTo<decimal>()).CastTo<T>());
            } else if (typeof(T) == typeof(double)) {
                ae = ((left, right) => (left.CastTo<double>() + right.CastTo<double>()).CastTo<T>());
            } else if (typeof(T) == typeof(float)) {
                ae = ((left, right) => (left.CastTo<float>() + right.CastTo<float>()).CastTo<T>());
            } else if (typeof(T) == typeof(int)) {
                ae = ((left, right) => (left.CastTo<int>() + right.CastTo<int>()).CastTo<T>());
            } else if (typeof(T) == typeof(long)) {
                ae = ((left, right) => (left.CastTo<long>() + right.CastTo<long>()).CastTo<T>());
            } else if (typeof(T) == typeof(sbyte)) {
                ae = ((left, right) => (left.CastTo<sbyte>() + right.CastTo<sbyte>()).CastTo<T>());
            } else if (typeof(T) == typeof(short)) {
                ae = ((left, right) => (left.CastTo<short>() + right.CastTo<short>()).CastTo<T>());
            } else if (typeof(T) == typeof(uint)) {
                ae = ((left, right) => (left.CastTo<uint>() + right.CastTo<uint>()).CastTo<T>());
            } else if (typeof(T) == typeof(ulong)) {
                ae = ((left, right) => (left.CastTo<ulong>() + right.CastTo<ulong>()).CastTo<T>());
            } else if (typeof(T) == typeof(ushort)) {
                ae = ((left, right) => (left.CastTo<ushort>() + right.CastTo<ushort>()).CastTo<T>());
            } else if (typeof(T) == typeof(Complex)) {
                ae = ((left, right) => (left.CastTo<Complex>() + right.CastTo<Complex>()).CastTo<T>());
            } else if (typeof(T) == typeof(BigInteger)) {
                ae = ((left, right) => (left.CastTo<BigInteger>() + right.CastTo<BigInteger>()).CastTo<T>());
            }

            return ae;
        }

        private static GetElementMultiplicationIdentity GetElementMultiplicationId() {
            GetElementMultiplicationIdentity gemi = null;

            if (typeof(T) == typeof(byte)) {
                gemi = (() => (1).CastTo<T>());
            } else if (typeof(T) == typeof(decimal)) {
                gemi = (() => (1).CastTo<T>());
            } else if (typeof(T) == typeof(double)) {
                gemi = (() => (1).CastTo<T>());
            } else if (typeof(T) == typeof(float)) {
                gemi = (() => (1).CastTo<T>());
            } else if (typeof(T) == typeof(int)) {
                gemi = (() => (1).CastTo<T>());
            } else if (typeof(T) == typeof(long)) {
                gemi = (() => (1).CastTo<T>());
            } else if (typeof(T) == typeof(sbyte)) {
                gemi = (() => (1).CastTo<T>());
            } else if (typeof(T) == typeof(short)) {
                gemi = (() => (1).CastTo<T>());
            } else if (typeof(T) == typeof(uint)) {
                gemi = (() => (1).CastTo<T>());
            } else if (typeof(T) == typeof(ulong)) {
                gemi = (() => (1).CastTo<T>());
            } else if (typeof(T) == typeof(ushort)) {
                gemi = (() => (1).CastTo<T>());
            } else if (typeof(T) == typeof(Complex)) {
                gemi = (() => (1).CastTo<T>());
            } else if (typeof(T) == typeof(BigInteger)) {
                gemi = (() => (1).CastTo<T>());
            }

            return gemi;
        }

        private static GetElementSumIdentity GetElementSumId() {
            GetElementSumIdentity gesi = null;

            if (typeof(T) == typeof(byte)) {
                gesi = (() => (0).CastTo<T>());
            } else if (typeof(T) == typeof(decimal)) {
                gesi = (() => (0).CastTo<T>());
            } else if (typeof(T) == typeof(double)) {
                gesi = (() => (0).CastTo<T>());
            } else if (typeof(T) == typeof(float)) {
                gesi = (() => (0).CastTo<T>());
            } else if (typeof(T) == typeof(int)) {
                gesi = (() => (0).CastTo<T>());
            } else if (typeof(T) == typeof(long)) {
                gesi = (() => (0).CastTo<T>());
            } else if (typeof(T) == typeof(sbyte)) {
                gesi = (() => (0).CastTo<T>());
            } else if (typeof(T) == typeof(short)) {
                gesi = (() => (0).CastTo<T>());
            } else if (typeof(T) == typeof(uint)) {
                gesi = (() => (0).CastTo<T>());
            } else if (typeof(T) == typeof(ulong)) {
                gesi = (() => (0).CastTo<T>());
            } else if (typeof(T) == typeof(ushort)) {
                gesi = (() => (0).CastTo<T>());
            } else if (typeof(T) == typeof(Complex)) {
                gesi = (() => (0).CastTo<T>());
            } else if (typeof(T) == typeof(BigInteger)) {
                gesi = (() => (0).CastTo<T>());
            }

            return gesi;
        }
        #endregion
        #endregion

        #region operators
        public static T operator +(T left, T right) {
            AddElement ae = GetAddElement();

            if (ae == null) {
                throw new NullReferenceException("The addition operation was not found.");
            }

            return ae(left, right);
        }

        //public static Matrix operator +(Matrix first, Matrix second) {
        //    return first.Add(second);
        //}

        public static T operator *(T left, T right) {
            MultiplyElement me = GetMultiplyElement();

            if (me == null) {
                throw new NullReferenceException("The multiplication operation was not found.");
            }

            return me(left, right);
        }

        //public static Matrix operator *(Matrix first, Matrix second) {
        //    return first.Multiply(second);
        //}

        //public static Matrix operator *(Matrix first, Complex scalar) {
        //    return first.MultiplyNumeric(scalar);
        //}

        //public static Matrix operator *(Matrix first, BigInteger scalar) {
        //    return first.MultiplyBigNumeric(scalar);
        //}

        //public static Matrix operator -(Matrix first, Matrix second) {
        //    int multiplier = -1;

        //    if (first.ElementType == ElementTypes.BigNumeric) {
        //        return first + (second * ((BigInteger)multiplier));
        //    } else if (first.ElementType == ElementTypes.Numeric) {
        //        return first + (second * ((Complex)multiplier));
        //    }

        //    return null;
        //}

        //public static bool operator ==(Matrix first, Matrix second) {
        //    return first.Equals(second);
        //}

        //public static bool operator !=(Matrix first, Matrix second) {
        //    return !(first.Equals(second));
        //}
        #endregion
    }
}
